package models;

import java.util.Iterator;
import java.util.Map;
import models.exceptions.PublicException;
import models.messages.CloseRoom;
import models.messages.IsRoomCreated;
import models.messages.FutureJoin;
import models.messages.GameJoin;
import models.messages.RoomJoin;

import com.jhalt.expiringmap.ExpiringMap;

import play.Logger;
import play.libs.*;
import play.mvc.WebSocket;
import scala.concurrent.Await;
import scala.concurrent.duration.Duration;
import akka.actor.*;
import static akka.pattern.Patterns.ask;
import static java.util.concurrent.TimeUnit.*;

import com.fasterxml.jackson.databind.JsonNode;

public class GameManager extends UntypedActor
{
    static ActorRef self = Akka.system().actorOf(
                                 Props.create(GameManager.class));

    public static boolean roomCreated() throws Exception
    {
        return (boolean) Await.result(ask(self, new IsRoomCreated(), 1000),
                Duration.create(1, SECONDS));
    }

    public static String futureJoin(String username, String type2,
            String type3, String type4, int initCash, int initFee)
            throws Exception
    {
        return (String) Await.result(
                ask(self, new FutureJoin(username, type2, type3, type4,
                        initCash, initFee), 1000), Duration.create(1, SECONDS));
    }

    public static void gameJoin(String uuid, WebSocket.In<JsonNode> in,
            WebSocket.Out<JsonNode> out) throws Exception
    {
        Await.result(ask(self, new GameJoin(uuid, in, out), 1000),
                Duration.create(1, SECONDS));
    }

    Map<String, FutureRoom> futureRooms = ExpiringMap.builder()
                                                .expiration(30, SECONDS)
                                                .build();

    ActorRef                room        = null;

    @Override
    public void onReceive(Object msg) throws Exception
    {
        try
        {
            if (msg instanceof FutureJoin)
            {
                FutureJoin futureJoin = (FutureJoin) msg;

                Iterator<String> it = this.futureRooms.keySet().iterator();
                while (it.hasNext())
                {
                    String uuid = it.next();
                    if (futureJoin.name
                            .equals(this.futureRooms.get(uuid).ownerName))
                    {
                        throw new PublicException(
                                "This username is already taken.");
                    }
                }
                FutureRoom futureRoom = new FutureRoom(futureJoin.name,
                        futureJoin.type2, futureJoin.type3, futureJoin.type4,
                        futureJoin.initCash, futureJoin.initFee);
                String uuid = java.util.UUID.randomUUID().toString();
                Logger.info("[ GameManager ] Future join (" + futureJoin.name
                        + ") " + futureRoom.opponents.toString() + ".");
                this.futureRooms.put(uuid, futureRoom);
                this.getSender().tell(uuid, this.getSelf());
            }
            else if (msg instanceof IsRoomCreated)
            {
                this.getSender().tell(this.room != null, this.getSelf());
            }
            else if (msg instanceof GameJoin)
            {
                GameJoin gameJoin = (GameJoin) msg;
                Logger.info("[ GameManager ] Game join (" + gameJoin.uuid + ")");
                if (!this.futureRooms.containsKey(gameJoin.uuid))
                {
                    throw new PublicException(
                            "I don't know you. Use form to join the game.");
                }
                if (this.room == null)
                {
                    Logger.info("[ GameManager ] Creating new room.");
                    this.room = Akka
                            .system()
                            .actorOf(
                                    Props.create(
                                            GameRoom.class,
                                            this.futureRooms.get(gameJoin.uuid).initCash,
                                            this.futureRooms.get(gameJoin.uuid).initFee));
                }
                this.room
                        .tell(new RoomJoin(gameJoin.uuid, this.futureRooms
                                .get(gameJoin.uuid), gameJoin.in, gameJoin.out),
                                this.getSelf());
                this.futureRooms.clear();
                this.getSender().tell("OK", this.getSelf());
            }
            else if (msg instanceof CloseRoom)
            {
                this.room.tell(akka.actor.PoisonPill.getInstance(),
                        this.getSelf());
                this.room = null;
                Logger.info("[ GameManager ] Closing game room.");
            }
            else if (msg instanceof akka.actor.Status.Failure)
            {
                Logger.error("[ GameManager ] Failure message ("
                        + msg.toString() + ").");
            }
            else
            {
                Logger.error("[ GameManager ] Unhandled message ("
                        + msg.getClass().getName() + ").");
                this.unhandled(msg);
            }
        }
        catch (PublicException e)
        {
            Logger.error("[ GameManager ] " + e.getMessage());
            this.getSender().tell(new akka.actor.Status.Failure(e),
                    this.getSelf());
        }
        catch (Exception e)
        {
            this.getSender().tell(new akka.actor.Status.Failure(e),
                    this.getSelf());
            throw e;
        }

    }
}
